package se.webbzon.oschi01.worldobject.appearance;

import java.awt.Color;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.Material;
import javax.media.j3d.RenderingAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
//import javax.media.j3d.Texture;
import javax.media.j3d.TextureAttributes;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransparencyAttributes;
//import javax.vecmath.AxisAngle4d;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Color3f;
import javax.vecmath.Vector3d;

import com.sun.j3d.utils.geometry.ColorCube;

import se.webbzon.oschi01.resourceset.ResourceIterator;
import se.webbzon.oschi01.resourceset.ResourceSet;
import se.webbzon.oschi01.textureset.Texture3D;

public class SimpleAppearance3D extends AbstractAppearance3D {
	
	// The layer separation coefficient (m/layer)
	private static double layerSeparation = 0.1;
	
	// Simple appearance properties
	public static final int	TRANSPARENCY		= (1<<0);
	public static final int TEXTURE_TRANSLATION	= (1<<1);
	public static final int	TEXTURE_REPEATING	= (1<<2);
	public static final int	TEXTURE_ROTATION	= (1<<3);
	public static final int LAYERS				= (1<<4);
	
	// The properties of this simple appearance
	private int properties;
	
	// The speed and index of the texture
	//private ResourceIterator<Texture3D> it;
	private double textureSpeed, textureIndex;
	
	// The set of images used as texture
	private ResourceSet<Texture3D> textureSet;
	
	// The model used to represent the appearance
	private Shape3D model;
	
	// The texture transform
	private final Transform3D textureTransform;
	
	// The translation, scaling and rotation of the texture
	private final Vector3d textureVector;
	private final AxisAngle4d textureRotation;
	
	// The layer vector
	private final Vector3d layerVector;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new WorldObjectSimpleAppearance from a geometry model. The 
	 * WorldObjectSimpleAppearance can be given a set of special properties based
	 * on the purpose of the appearance. If the properties are set to zero then
	 * no special properties will be applied. **/
	protected SimpleAppearance3D(	int renderingOrder, 
									int simpleAppearanceProperties, 
									Geometry model) {
		super(renderingOrder);
		this.properties = simpleAppearanceProperties;
		this.model = new PrivateModel(simpleAppearanceProperties, model);
		textureSpeed = 0;
		textureIndex = 0;
		//it = null;
		textureSet = null;
		
		// Initialize texture vector
		if (isSet(simpleAppearanceProperties,TEXTURE_TRANSLATION) ||
			isSet(simpleAppearanceProperties,TEXTURE_REPEATING))
			textureVector = new Vector3d();
		else
			textureVector = null;
		
		// Initialize texture rotation
		if (isSet(simpleAppearanceProperties,TEXTURE_ROTATION))
			textureRotation = new AxisAngle4d();
		else
			textureRotation = null;
		
		// Initialize texture transform only if needed
		if (	textureVector != null ||
				textureRotation != null)
			textureTransform = new Transform3D();
		else
			textureTransform = null;
		
		// Initialize layer vector
		if (isSet(simpleAppearanceProperties,LAYERS))
			layerVector = new Vector3d();
		else
			layerVector = null;
		
		// Add the model to the transform group
		getTransformGroup().addChild(this.model);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Sets the layers separation coefficient. The default layer
	 * separation coefficient is 0.01 m/layer. **/
	public static void setLayerSeparation(double separation) {
		layerSeparation = separation;
	}
	
	/** Returns the layers separation coefficient. **/
	public static double getLayerSeparation() {
		return layerSeparation;
	}
	
	/** Returns true if the simple appearance supports layers. **/
	public boolean hasLayers() {
		return isSet(properties,LAYERS);
	}
	
	/** Returns true if the simple appearance supports texture repeating. **/
	public boolean hasTextureRepeating() {
		return isSet(properties,TEXTURE_REPEATING);
	}
	
	/** Returns true if the simple appearance supports texture translation. **/
	public boolean hasTextureTranslation() {
		return isSet(properties,TEXTURE_TRANSLATION);
	}
	
	/** Returns true if the simple appearance supports texture rotation. **/
	public boolean hasTextureRotation() {
		return isSet(properties,TEXTURE_ROTATION);
	}
	
	/** Returns true if the simple appearance supports texture transparency. **/
	public boolean hasTransparency() {
		return isSet(properties,TRANSPARENCY);
	}
	
	/** Returns the update speed of the texture. If speed is equal to
	 * 1 then the texture will be updated once per step. **/
	public double getTextureSpeed() {
		//return it == null ? 0 : it.getSpeed();
		return textureSpeed;
	}
	
	/** Sets the update speed of the texture. If speed is equal to
	 * 1 then the texture will be updated once per step. **/
	public void setTextureSpeed(double imageSpeed) {
		/*if (it != null)
			it.setSpeed(imageSpeed); */
		if (imageSpeed >= 0)
			this.textureSpeed = imageSpeed;
	}
	
	/** Returns the index of the current displayed texture.
	 * The real index of the current texture is the returned
	 * value floored. */
	public double getTextureIndex() {
		//return it == null ? 0 : it.getIndex();
		return textureIndex;
	}
	
	/** Sets the index of the current displayed texture.
	 * The real index of the current texture is the set
	 * value floored. */
	public void setTextureIndex(double textureIndex) {
		/*if (it != null && it.getIndex() != textureIndex) {
			it.setIndex(textureIndex);
			model.getAppearance().setTexture(it.current().getTexture());
		}*/
		if (	textureIndex != this.textureIndex && 
				textureIndex >= 0 && 
				textureIndex < textureSet.size()) {
			this.textureIndex = textureIndex;
			final Texture texture = textureSet.get((int) textureIndex).getTexture();
			model.getAppearance().setTexture(texture);
		}
	}
	
	/** Sets the set of textures used as texture. If the texture set
	 * is changed then the texture index will be set to zero. **/
	public void setTextureSet(ResourceSet<Texture3D> textureSet) {
		/*if (textureSet != this.textureSet) {
			double speed = it == null ? 0 : it.getSpeed();
			it = new ResourceIterator<Texture3D>(textureSet);
			it.setSpeed(speed);
			model.getAppearance().setTexture(textureSet.get(0).getTexture());
		}*/
		if (textureSet != this.textureSet) {
			textureIndex = 0;
			this.textureSet = textureSet;
			model.getAppearance().setTexture(textureSet.get(0).getTexture());
		}
	}
	
	/** Returns a the set of textures used as texture. **/
	public ResourceSet<Texture3D> getTextureSet() {
		return textureSet;
	}
	
	/** Sets the color of the simple appearance. **/
	public void setColor(Color color) {
		final Color3f c = new Color3f(color);
		model.getAppearance().getMaterial().setAmbientColor(c);
		model.getAppearance().getMaterial().setDiffuseColor(c);
	}
	
	/** Returns the color of the simple appearance. **/
	public Color getColor() {
		final Color3f color = new Color3f();
		model.getAppearance().getMaterial().getAmbientColor(color);
		return color.get();
	}
	
	/** Sets the transparency value of the simple appearance. **/
	public void setTransparency(float transparency) {
		model.getAppearance().getTransparencyAttributes().setTransparency(transparency);
	}
	
	/** Returns the transparency value of the simple appearance. **/
	public float getTransparency() {
		return model.getAppearance().getTransparencyAttributes().getTransparency();
	}
	
	/** Sets the number of horizontal texture repeats. **/
	public void setHRep(double hRep) {
		textureTransform.getScale(textureVector);
		if (hRep != textureVector.x) {
			textureVector.x = hRep;
			textureTransform.setScale(textureVector);
			model.getAppearance().getTextureAttributes().setTextureTransform(textureTransform);
		}
	}
	
	/** Returns the number of horizontal texture repeats. **/
	public double getHRep() {
		textureTransform.getScale(textureVector);
		return textureVector.x;
	}
	
	/** Sets the number of vertical texture repeats. **/
	public void setVRep(double vRep) {
		textureTransform.getScale(textureVector);
		if (vRep != textureVector.y) {
			textureVector.y = vRep;
			textureTransform.setScale(textureVector);
			model.getAppearance().getTextureAttributes().setTextureTransform(textureTransform);
		}
	}
	
	/** Returns the number of vertical texture repeats. **/
	public double getVRep() {
		textureTransform.getScale(textureVector);
		return textureVector.y;
	}
	
	/** Sets the horizontal offset of the texture. **/
	public void setHOffset(double hOffset) {
		textureTransform.get(textureVector);
		if (-hOffset != textureVector.x) {
			textureVector.x = -hOffset;
			textureTransform.setTranslation(textureVector);
			model.getAppearance().getTextureAttributes().setTextureTransform(textureTransform);
		}
	}
	
	/** Returns the horizontal offset of the texture. **/
	public double getHOffset() {
		textureTransform.get(textureVector);
		return -textureVector.x;
	}
	
	/** Sets the horizontal offset of the texture. **/
	public void setVOffset(double vOffset) {
		textureTransform.get(textureVector);
		if (-vOffset != textureVector.y) {
			textureVector.y = -vOffset;
			textureTransform.setTranslation(textureVector);
			model.getAppearance().getTextureAttributes().setTextureTransform(textureTransform);
		}
	}
	
	/** Returns the horizontal offset of the texture. **/
	public double getVOffset() {
		textureTransform.get(textureVector);
		return -textureVector.y;
	}
	
	/** Sets the rotation of the texture. **/
	public void setRotation(double angle) {
		if (-angle != textureRotation.angle) {
			textureRotation.angle = -angle;
			textureTransform.setRotation(textureRotation);
			model.getAppearance().getTextureAttributes().setTextureTransform(textureTransform);
		}
	}
	
	/** Returns the rotation of the texture. **/
	public double getRotation() {
		return -textureRotation.angle;
	}
	
	/** Sets the rendering layer of this simple appearance. **/
	public void setLayer(double xLayer, double yLayer, double zLayer) {
		if (	xLayer != layerVector.x || yLayer != layerVector.y || 
				zLayer != layerVector.z) {
			layerVector.set(xLayer,yLayer,zLayer);
			Transform3D transform = new Transform3D();
			getTransform(transform);
			setTransform(transform);
		}
	}
	
	/** Returns the vector which describes the rendering layer of this 
	 * simple appearance. **/
	public void getLayer(Vector3d layerVector) {
		this.layerVector.get(layerVector);
	}
	
	@Override public void setTransform(Transform3D transform) {
		if (layerVector != null) {
			final Vector3d translation = new Vector3d();
			transform.get(translation);
			translation.scaleAdd(layerSeparation,layerVector,translation);
			transform.setTranslation(translation);
		}
		super.setTransform(transform);
	}
	
	@Override public boolean getVisible() {
		return model.getAppearance().getRenderingAttributes().getVisible();
	}
	
	@Override public void setVisible(boolean visible) {
		model.getAppearance().getRenderingAttributes().setVisible(visible);
	}
	
	@Override public void onStepEnd() {
		super.onStepEnd();
		/*if (it != null)
			model.getAppearance().setTexture(it.next().getTexture());*/
		if (textureSet != null && textureSpeed > 0) {
			double textureIndex = this.textureIndex + textureSpeed;
			if (textureIndex >= textureSet.size())
				textureIndex -= textureSet.size();
			setTextureIndex(textureIndex);
		}
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns true if the given property is set in the given properties. **/
	private static final boolean isSet(int properties, int property) {
		return ((properties & property) == property);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	private static class PrivateModel extends Shape3D {
		
		/*============================================================
		Constructors
		============================================================*/
		
		/** Creates a new model from a 3D geometry. **/
		public PrivateModel(int properties, Geometry model) {
			super(model);
			Appearance appearance = new Appearance();
			appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
			
			// Add material to the simple appearance
			Material mat = new Material();
			mat.setCapability(Material.ALLOW_COMPONENT_READ);
			mat.setCapability(Material.ALLOW_COMPONENT_WRITE);
			mat.setAmbientColor(new Color3f(0.9f,0.9f,0.9f));
			mat.setDiffuseColor(new Color3f(0.9f,0.9f,0.9f));
			mat.setSpecularColor(new Color3f(0.0f,0.0f,0.0f));
			mat.setEmissiveColor(new Color3f(0.0f,0.0f,0.0f));
			appearance.setMaterial(mat);
			
			// Add rendering attributes to the simple appearance
			RenderingAttributes ra = new RenderingAttributes();
			ra.setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
			ra.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
			ra.setAlphaTestFunction(RenderingAttributes.NOT_EQUAL);
			ra.setAlphaTestValue(0.0f);
			appearance.setRenderingAttributes(ra);
			
			// Add texture attributes to the simple appearance
			TextureAttributes ta = new TextureAttributes();
			ta.setCapability(TextureAttributes.ALLOW_TRANSFORM_READ);
			ta.setCapability(TextureAttributes.ALLOW_TRANSFORM_WRITE);
			ta.setTextureMode(TextureAttributes.MODULATE);
			appearance.setTextureAttributes(ta);
			
			if (isSet(properties,TRANSPARENCY)) {
				// Add transparency attributes to the simple appearance
				TransparencyAttributes tra = new TransparencyAttributes();
				tra.setCapability(TransparencyAttributes.ALLOW_VALUE_READ);
				tra.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
				tra.setTransparencyMode(TransparencyAttributes.BLENDED);
				tra.setTransparency(0.0f);
				appearance.setTransparencyAttributes(tra);
			}
			
			setAppearance(appearance);
		}
		
	}

}
